{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Seaborn 经验累积分布 (ecdfplot) 完整教程\n",
    "\n",
    "本教程详细讲解 Seaborn 中经验累积分布图的使用方法，包括基础绘图、互补CDF、统计方式以及实际应用。\n",
    "\n",
    "## 目录\n",
    "1. 基础经验累积分布\n",
    "2. 统计方式 (stat)\n",
    "3. 互补CDF (complementary)\n",
    "4. 分组对比\n",
    "5. 实际应用案例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# 设置样式\n",
    "sns.set_theme(style=\"whitegrid\")\n",
    "plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "# 加载示例数据\n",
    "penguins = sns.load_dataset(\"penguins\")\n",
    "tips = sns.load_dataset(\"tips\")\n",
    "diamonds = sns.load_dataset(\"diamonds\")\n",
    "\n",
    "print(\"企鹅数据集预览：\")\n",
    "penguins.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 1. 基础经验累积分布\n",
    "\n",
    "### 1.1 什么是经验累积分布函数（ECDF）？\n",
    "\n",
    "**经验累积分布函数（Empirical Cumulative Distribution Function, ECDF）** 是一种非参数方法，用于估计随机变量的累积分布函数。\n",
    "\n",
    "**定义**：对于样本 x₁, x₂, ..., xₙ，ECDF 在点 t 处的值为：\n",
    "```\n",
    "F(t) = (小于等于 t 的样本数) / (总样本数)\n",
    "```\n",
    "\n",
    "**特点**：\n",
    "- 不需要假设分布类型\n",
    "- 阶梯函数，在每个数据点处跳跃\n",
    "- 直接反映数据的累积情况"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10, 6))\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\")\n",
    "plt.title(\"企鹅鳍长度的经验累积分布\", fontsize=14)\n",
    "plt.xlabel(\"鳍长度 (mm)\")\n",
    "plt.ylabel(\"累积比例\")\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 ECDF 的解读\n",
    "\n",
    "从 ECDF 图中可以读取：\n",
    "- **中位数**：y=0.5 对应的 x 值\n",
    "- **四分位数**：y=0.25 和 y=0.75 对应的 x 值\n",
    "- **百分位数**：任意 y 值对应的 x 值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10, 6))\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\")\n",
    "\n",
    "# 标记关键分位数\n",
    "quantiles = [0.25, 0.5, 0.75]\n",
    "values = penguins['flipper_length_mm'].quantile(quantiles)\n",
    "\n",
    "colors = ['green', 'red', 'blue']\n",
    "labels = ['Q1 (25%)', '中位数 (50%)', 'Q3 (75%)']\n",
    "\n",
    "for q, v, c, l in zip(quantiles, values, colors, labels):\n",
    "    plt.axhline(q, color=c, linestyle='--', alpha=0.5)\n",
    "    plt.axvline(v, color=c, linestyle='--', alpha=0.5, label=f\"{l}: {v:.1f}\")\n",
    "\n",
    "plt.title(\"ECDF 与分位数标记\", fontsize=14)\n",
    "plt.xlabel(\"鳍长度 (mm)\")\n",
    "plt.ylabel(\"累积比例\")\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 ECDF vs KDE 累积分布对比"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
    "\n",
    "# ECDF（阶梯函数）\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\", ax=axes[0])\n",
    "axes[0].set_title(\"ECDF（阶梯函数，精确）\")\n",
    "axes[0].set_ylabel(\"累积比例\")\n",
    "\n",
    "# KDE 累积分布（平滑曲线）\n",
    "sns.kdeplot(data=penguins, x=\"flipper_length_mm\", \n",
    "            cumulative=True, ax=axes[1])\n",
    "axes[1].set_title(\"KDE 累积分布（平滑曲线，估计）\")\n",
    "axes[1].set_ylabel(\"累积概率\")\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"区别：\")\n",
    "print(\"- ECDF: 阶梯状，直接基于数据，无参数\")\n",
    "print(\"- KDE累积: 平滑曲线，基于核密度估计，有带宽参数\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 2. 统计方式 (stat)\n",
    "\n",
    "### 2.1 stat 参数说明\n",
    "\n",
    "`stat` 参数控制 y 轴的统计方式：\n",
    "- `'proportion'`（默认）：累积比例（0-1）\n",
    "- `'count'`：累积计数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
    "\n",
    "# 累积比例\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\", \n",
    "             stat='proportion', ax=axes[0])\n",
    "axes[0].set_title(\"stat='proportion' (累积比例)\")\n",
    "axes[0].set_ylabel(\"累积比例\")\n",
    "\n",
    "# 累积计数\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\", \n",
    "             stat='count', ax=axes[1])\n",
    "axes[1].set_title(\"stat='count' (累积计数)\")\n",
    "axes[1].set_ylabel(\"累积数量\")\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 应用场景\n",
    "\n",
    "- **proportion**：比较不同样本量的数据\n",
    "- **count**：查看绝对数量的累积"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 3. 互补CDF (complementary)\n",
    "\n",
    "### 3.1 什么是互补CDF？\n",
    "\n",
    "**互补累积分布函数（Complementary CDF, CCDF）** 也称为**生存函数**，定义为：\n",
    "```\n",
    "S(t) = 1 - F(t) = P(X > t)\n",
    "```\n",
    "\n",
    "表示随机变量**大于** t 的概率。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
    "\n",
    "# CDF\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\", \n",
    "             complementary=False, ax=axes[0])\n",
    "axes[0].set_title(\"CDF: P(X ≤ x)\")\n",
    "axes[0].set_ylabel(\"累积比例 (≤)\")\n",
    "\n",
    "# CCDF\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\", \n",
    "             complementary=True, ax=axes[1])\n",
    "axes[1].set_title(\"CCDF: P(X > x)\")\n",
    "axes[1].set_ylabel(\"累积比例 (>)\")\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2 互补CDF的应用\n",
    "\n",
    "互补CDF在以下场景特别有用：\n",
    "- **生存分析**：研究存活时间\n",
    "- **可靠性工程**：研究失效时间\n",
    "- **尾部分析**：关注极端值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 示例：分析高消费客户\n",
    "plt.figure(figsize=(10, 6))\n",
    "sns.ecdfplot(data=tips, x=\"total_bill\", complementary=True)\n",
    "\n",
    "# 标记高消费阈值\n",
    "threshold = 30\n",
    "proportion_above = (tips['total_bill'] > threshold).mean()\n",
    "\n",
    "plt.axvline(threshold, color='red', linestyle='--', \n",
    "            label=f'阈值: ${threshold}')\n",
    "plt.axhline(proportion_above, color='red', linestyle='--', alpha=0.5,\n",
    "            label=f'超过阈值比例: {proportion_above:.2%}')\n",
    "\n",
    "plt.title(\"账单金额的互补CDF（生存函数）\", fontsize=14)\n",
    "plt.xlabel(\"账单总额 ($)\")\n",
    "plt.ylabel(\"P(账单 > x)\")\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()\n",
    "\n",
    "print(f\"解读：有 {proportion_above:.2%} 的客户账单超过 ${threshold}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 4. 分组对比\n",
    "\n",
    "### 4.1 使用 hue 分组"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10, 6))\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\", hue=\"species\")\n",
    "plt.title(\"不同企鹅物种的鳍长度累积分布\", fontsize=14)\n",
    "plt.xlabel(\"鳍长度 (mm)\")\n",
    "plt.ylabel(\"累积比例\")\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2 分组对比的优势\n",
    "\n",
    "ECDF 在分组对比时特别有用：\n",
    "- 清晰展示分布差异\n",
    "- 易于比较中位数和分位数\n",
    "- 不受区间划分影响"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 对比不同时间段的账单分布\n",
    "plt.figure(figsize=(10, 6))\n",
    "sns.ecdfplot(data=tips, x=\"total_bill\", hue=\"time\", linewidth=2)\n",
    "\n",
    "# 标记中位数\n",
    "for time, color in zip(['Lunch', 'Dinner'], ['C0', 'C1']):\n",
    "    median = tips[tips['time'] == time]['total_bill'].median()\n",
    "    plt.axvline(median, color=color, linestyle='--', alpha=0.5,\n",
    "                label=f'{time} 中位数: ${median:.2f}')\n",
    "\n",
    "plt.title(\"午餐 vs 晚餐账单分布对比\", fontsize=14)\n",
    "plt.xlabel(\"账单总额 ($)\")\n",
    "plt.ylabel(\"累积比例\")\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 5. 实际应用案例\n",
    "\n",
    "### 5.1 案例1：钻石价格分析"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 采样以提高性能\n",
    "diamonds_sample = diamonds.sample(n=5000, random_state=42)\n",
    "\n",
    "plt.figure(figsize=(12, 6))\n",
    "sns.ecdfplot(data=diamonds_sample, x=\"price\", hue=\"cut\")\n",
    "\n",
    "plt.title(\"不同切工钻石的价格累积分布\", fontsize=14)\n",
    "plt.xlabel(\"价格 ($)\")\n",
    "plt.ylabel(\"累积比例\")\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()\n",
    "\n",
    "print(\"观察：可以清晰看出不同切工的价格分布差异\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2 案例2：A/B测试对比"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 模拟A/B测试数据\n",
    "np.random.seed(42)\n",
    "group_a = np.random.normal(100, 15, 500)\n",
    "group_b = np.random.normal(105, 15, 500)\n",
    "\n",
    "ab_test = pd.DataFrame({\n",
    "    'value': np.concatenate([group_a, group_b]),\n",
    "    'group': ['A'] * 500 + ['B'] * 500\n",
    "})\n",
    "\n",
    "plt.figure(figsize=(10, 6))\n",
    "sns.ecdfplot(data=ab_test, x=\"value\", hue=\"group\", linewidth=2.5)\n",
    "\n",
    "plt.title(\"A/B测试：两组数据分布对比\", fontsize=14)\n",
    "plt.xlabel(\"指标值\")\n",
    "plt.ylabel(\"累积比例\")\n",
    "plt.grid(alpha=0.3)\n",
    "plt.legend(title=\"测试组\")\n",
    "plt.show()\n",
    "\n",
    "print(\"应用：ECDF 可以直观展示两组数据在各个分位数上的差异\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3 案例3：质量控制"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用企鹅数据模拟质量控制场景\n",
    "plt.figure(figsize=(10, 6))\n",
    "sns.ecdfplot(data=penguins, x=\"body_mass_g\", hue=\"species\")\n",
    "\n",
    "# 设定质量标准范围\n",
    "lower_spec = 3500\n",
    "upper_spec = 5000\n",
    "\n",
    "plt.axvline(lower_spec, color='red', linestyle='--', alpha=0.5, label='下限')\n",
    "plt.axvline(upper_spec, color='red', linestyle='--', alpha=0.5, label='上限')\n",
    "plt.axvspan(lower_spec, upper_spec, alpha=0.1, color='green', label='合格范围')\n",
    "\n",
    "plt.title(\"体重分布与质量标准对比\", fontsize=14)\n",
    "plt.xlabel(\"体重 (g)\")\n",
    "plt.ylabel(\"累积比例\")\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()\n",
    "\n",
    "# 计算合格率\n",
    "for species in penguins['species'].unique():\n",
    "    data = penguins[penguins['species'] == species]['body_mass_g'].dropna()\n",
    "    in_range = ((data >= lower_spec) & (data <= upper_spec)).mean()\n",
    "    print(f\"{species}: 合格率 = {in_range:.2%}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.4 综合对比：多种分布图"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n",
    "\n",
    "# 1. 直方图\n",
    "sns.histplot(data=penguins, x=\"flipper_length_mm\", \n",
    "             hue=\"species\", kde=True, ax=axes[0, 0])\n",
    "axes[0, 0].set_title(\"直方图 + KDE\")\n",
    "\n",
    "# 2. KDE\n",
    "sns.kdeplot(data=penguins, x=\"flipper_length_mm\", \n",
    "            hue=\"species\", fill=True, alpha=0.5, ax=axes[0, 1])\n",
    "axes[0, 1].set_title(\"核密度估计\")\n",
    "\n",
    "# 3. KDE 累积\n",
    "sns.kdeplot(data=penguins, x=\"flipper_length_mm\", \n",
    "            hue=\"species\", cumulative=True, ax=axes[1, 0])\n",
    "axes[1, 0].set_title(\"KDE 累积分布\")\n",
    "\n",
    "# 4. ECDF\n",
    "sns.ecdfplot(data=penguins, x=\"flipper_length_mm\", \n",
    "             hue=\"species\", ax=axes[1, 1])\n",
    "axes[1, 1].set_title(\"经验累积分布 (ECDF)\")\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 总结\n",
    "\n",
    "### ECDF 的优势\n",
    "\n",
    "1. **无参数**：不需要假设分布类型\n",
    "2. **精确**：直接基于数据，无估计误差\n",
    "3. **易读**：直接读取分位数\n",
    "4. **对比清晰**：多组对比时优势明显\n",
    "\n",
    "### 参数选择指南\n",
    "\n",
    "1. **stat**：\n",
    "   - 比较不同样本：使用 `'proportion'`\n",
    "   - 查看绝对数量：使用 `'count'`\n",
    "\n",
    "2. **complementary**：\n",
    "   - 关注尾部/极端值：使用 `True`\n",
    "   - 常规分析：使用 `False`\n",
    "   - 生存分析：使用 `True`\n",
    "\n",
    "### 应用场景\n",
    "\n",
    "**适合使用 ECDF**：\n",
    "- A/B测试对比\n",
    "- 分位数分析\n",
    "- 质量控制\n",
    "- 生存分析\n",
    "- 多组分布对比\n",
    "\n",
    "**不适合 ECDF**：\n",
    "- 需要平滑曲线时（用KDE）\n",
    "- 需要查看密度峰值时（用直方图或KDE）\n",
    "- 数据量极小时（<20）\n",
    "\n",
    "### 最佳实践\n",
    "\n",
    "- 用于分组对比时效果最佳\n",
    "- 结合分位数线标记关键点\n",
    "- 与其他分布图配合使用\n",
    "- 大数据集时性能优于直方图"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
